/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_shade_calc.c

#include "tr_local.h"

#define min(a, b) (((a) < (b)) ? (a) : (b))
#define	WAVEVALUE( table, base, amplitude, phase, freq )  ((base) + table[ (long)( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude))

static float *TableForFunc( genFunc_t func )
{
    switch ( func )
    {
    case GF_SIN:
        return tr.sinTable;
    case GF_TRIANGLE:
        return tr.triangleTable;
    case GF_SQUARE:
        return tr.squareTable;
    case GF_SAWTOOTH:
        return tr.sawToothTable;
    case GF_INVERSE_SAWTOOTH:
        return tr.inverseSawToothTable;
    case GF_NONE:
    default:
        break;
    }

    Com_Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'\n", func, tess.shader->name );
    return NULL;
}

/*
** EvalWaveForm
**
** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly
*/
static float EvalWaveForm( const waveForm_t *wf )
{
    float	*table;

    table = TableForFunc( wf->func );

    return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency );
}

static float EvalWaveFormClamped( const waveForm_t *wf )
{
    float glow  = EvalWaveForm( wf );

    if ( glow < 0 )
    {
        return 0;
    }

    if ( glow > 1 )
    {
        return 1;
    }

    return glow;
}

/*
** RB_CalcStretchTexCoords
*/
void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st )
{
    float p;
    texModInfo_t tmi;

    p = 1.0f / EvalWaveForm( wf );

    tmi.matrix[0][0] = p;
    tmi.matrix[1][0] = 0;
    tmi.translate[0] = 0.5f - 0.5f * p;

    tmi.matrix[0][1] = 0;
    tmi.matrix[1][1] = p;
    tmi.translate[1] = 0.5f - 0.5f * p;

    RB_CalcTransformTexCoords( &tmi, st );
}

/*
====================================================================
 
DEFORMATIONS
 
====================================================================
*/

/*
========================
RB_CalcDeformVertexes
 
========================
*/
void RB_CalcDeformVertexes( deformStage_t *ds )
{
    int i;
    vec3_t	offset;
    float	scale;
    float	*xyz = ( float * ) tess.xyz;
    float	*normal = ( float * ) tess.normal;
    float	*table;

    if ( ds->deformationWave.frequency == 0 )
    {
        scale = EvalWaveForm( &ds->deformationWave );

        for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
        {
            VectorScale( normal, scale, offset );

            xyz[0] += offset[0];
            xyz[1] += offset[1];
            xyz[2] += offset[2];
        }
    }
    else
    {
        table = TableForFunc( ds->deformationWave.func );

        for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
        {
            float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread;

            scale = WAVEVALUE( table, ds->deformationWave.base,
                               ds->deformationWave.amplitude,
                               ds->deformationWave.phase + off,
                               ds->deformationWave.frequency );

            VectorScale( normal, scale, offset );

            xyz[0] += offset[0];
            xyz[1] += offset[1];
            xyz[2] += offset[2];
        }
    }
}

/*
=========================
RB_CalcDeformNormals
 
Wiggle the normals for wavy environment mapping
=========================
*/
void RB_CalcDeformNormals( deformStage_t *ds )
{
    int i;
    float	scale;
    float	*xyz = ( float * ) tess.xyz;
    float	*normal = ( float * ) tess.normal;

    for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
    {
        scale = 0.98f;
        scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
                              tess.shaderTime * ds->deformationWave.frequency );
        normal[ 0 ] += ds->deformationWave.amplitude * scale;

        scale = 0.98f;
        scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
                              tess.shaderTime * ds->deformationWave.frequency );
        normal[ 1 ] += ds->deformationWave.amplitude * scale;

        scale = 0.98f;
        scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
                              tess.shaderTime * ds->deformationWave.frequency );
        normal[ 2 ] += ds->deformationWave.amplitude * scale;

        VectorNormalizeFast( normal );
    }
}

/*
========================
RB_CalcBulgeVertexes
 
========================
*/
void RB_CalcBulgeVertexes( deformStage_t *ds )
{
    int i;
    const float *st = ( const float * ) tess.texCoords[0];
    float		*xyz = ( float * ) tess.xyz;
    float		*normal = ( float * ) tess.normal;
    float		now;

    now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f;

    for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 )
    {
        int		off;
        float scale;

        off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now );

        scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight;

        xyz[0] += normal[0] * scale;
        xyz[1] += normal[1] * scale;
        xyz[2] += normal[2] * scale;
    }
}


/*
======================
RB_CalcMoveVertexes
 
A deformation that can move an entire surface along a wave path
======================
*/
void RB_CalcMoveVertexes( deformStage_t *ds )
{
    int			i;
    float		*xyz;
    float		*table;
    float		scale;
    vec3_t		offset;

    table = TableForFunc( ds->deformationWave.func );

    scale = WAVEVALUE( table, ds->deformationWave.base,
                       ds->deformationWave.amplitude,
                       ds->deformationWave.phase,
                       ds->deformationWave.frequency );

    VectorScale( ds->moveVector, scale, offset );

    xyz = ( float * ) tess.xyz;
    for ( i = 0; i < tess.numVertexes; i++, xyz += 4 )
    {
        VectorAdd( xyz, offset, xyz );
    }
}


/*
=============
DeformText
 
Change a polygon into a bunch of text polygons
=============
*/
void DeformText( const char *text )
{
    int		i;
    vec3_t	origin, width, height;
    int		len;
    int		ch;
    vec4_t	color;
    float	bottom, top;
    vec3_t	mid;

    height[0] = 0;
    height[1] = 0;
    height[2] = -1;
    CrossProduct( tess.normal[0], height, width );

    // find the midpoint of the box
    VectorClear( mid );
    bottom = 999999;
    top = -999999;
    for ( i = 0 ; i < 4 ; i++ )
    {
        VectorAdd( tess.xyz[i], mid, mid );
        if ( tess.xyz[i][2] < bottom )
        {
            bottom = tess.xyz[i][2];
        }
        if ( tess.xyz[i][2] > top )
        {
            top = tess.xyz[i][2];
        }
    }
    VectorScale( mid, 0.25f, origin );

    // determine the individual character size
    height[0] = 0;
    height[1] = 0;
    height[2] = ( top - bottom ) * 0.5f;

    VectorScale( width, height[2] * -0.75f, width );

    // determine the starting position
    len = strlen( text );
    VectorMA( origin, (len-1), width, origin );

    // clear the shader indexes
    tess.numIndexes = 0;
    tess.numVertexes = 0;

    color[0] = color[1] = color[2] = color[3] = 1.0;

    // draw each character
    for ( i = 0 ; i < len ; i++ )
    {
        ch = text[i];
        ch &= 255;

        if ( ch != ' ' )
        {
            int		row, col;
            float	frow, fcol, size;

            row = ch>>4;
            col = ch&15;

            frow = row*0.0625f;
            fcol = col*0.0625f;
            size = 0.0625f;

            RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size );
        }
        VectorMA( origin, -2, width, origin );
    }
}

/*
==================
GlobalVectorToLocal
==================
*/
static void GlobalVectorToLocal( const vec3_t in, vec3_t out )
{
    out[0] = DotProduct( in, backEnd.ori.axis[0] );
    out[1] = DotProduct( in, backEnd.ori.axis[1] );
    out[2] = DotProduct( in, backEnd.ori.axis[2] );
}

/*
=====================
AutospriteDeform
 
Assuming all the triangles for this shader are independant
quads, rebuild them as forward facing sprites
=====================
*/
static void AutospriteDeform( void )
{
    int		i;
    int		oldVerts;
    float	*xyz;
    vec3_t	mid, delta;
    float	radius;
    vec3_t	left, up;
    vec3_t	leftDir, upDir;

    if ( tess.numVertexes & 3 )
    {
        CL_RefPrintf( PRINT_WARNING, "Autosprite shader %s had odd vertex count", tess.shader->name );
    }
    if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 )
    {
        CL_RefPrintf( PRINT_WARNING, "Autosprite shader %s had odd index count", tess.shader->name );
    }

    oldVerts = tess.numVertexes;
    tess.numVertexes = 0;
    tess.numIndexes = 0;

    if ( backEnd.currentEntity != &tr.worldEntity )
    {
        GlobalVectorToLocal( backEnd.viewParms.ori.axis[1], leftDir );
        GlobalVectorToLocal( backEnd.viewParms.ori.axis[2], upDir );
    }
    else
    {
        VectorCopy( backEnd.viewParms.ori.axis[1], leftDir );
        VectorCopy( backEnd.viewParms.ori.axis[2], upDir );
    }

    for ( i = 0 ; i < oldVerts ; i+=4 )
    {
        // find the midpoint
        xyz = tess.xyz[i];

        mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]);
        mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]);
        mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]);

        VectorSubtract( xyz, mid, delta );
        radius = VectorLength( delta ) * 0.707f;		// / sqrt(2)

        VectorScale( leftDir, radius, left );
        VectorScale( upDir, radius, up );

        if ( backEnd.viewParms.isMirror )
            VectorSubtract( vec3_origin, left, left );

        // compensate for scale in the axes if necessary
        if ( backEnd.currentEntity->e.nonNormalizedAxes )
        {
            float axisLength;
#define VectorLength(v) (sqrt ((v)[0]*(v)[0] + (v)[1]*(v)[1] + (v)[2]*(v)[2]))
            axisLength = VectorLength( backEnd.currentEntity->e.axis[0] );
#undef VectorLength
            if ( !axisLength )
            {
                axisLength = 0;
            }
            else
            {
                axisLength = 1.0f / axisLength;
            }
            VectorScale(left, axisLength, left);
            VectorScale(up, axisLength, up);
        }

        RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] );
    }
}


/*
=====================
Autosprite2Deform
 
Autosprite2 will pivot a rectangular quad along the center of its long axis
=====================
*/
int edgeVerts[6][2] =
{
	{ 0, 1 },
	{ 0, 2 },
	{ 0, 3 },
	{ 1, 2 },
	{ 1, 3 },
	{ 2, 3 }
};

static void Autosprite2Deform( void )
{
    int		i, j, k;
    int		indexes;
    float	*xyz;
    vec3_t	forward;

    if ( tess.numVertexes & 3 )
        CL_RefPrintf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name );
    if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 )
        CL_RefPrintf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name );

    if ( backEnd.currentEntity != &tr.worldEntity )
        GlobalVectorToLocal( backEnd.viewParms.ori.axis[0], forward );
    else
        VectorCopy( backEnd.viewParms.ori.axis[0], forward );

    // this is a lot of work for two triangles...
    // we could precalculate a lot of it is an issue, but it would mess up
    // the shader abstraction
    for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 )
    {
        float	lengths[2];
        int		nums[2];
        vec3_t	mid[2];
        vec3_t	major, minor;
        float	*v1, *v2;

        // find the midpoint
        xyz = tess.xyz[i];

        // identify the two shortest edges
        nums[0] = nums[1] = 0;
        lengths[0] = lengths[1] = 999999;

        for ( j = 0 ; j < 6 ; j++ )
        {
            float	l;
            vec3_t	temp;

            v1 = xyz + 4 * edgeVerts[j][0];
            v2 = xyz + 4 * edgeVerts[j][1];

            VectorSubtract( v1, v2, temp );

            l = DotProduct( temp, temp );
            if ( l < lengths[0] )
            {
                nums[1] = nums[0];
                lengths[1] = lengths[0];
                nums[0] = j;
                lengths[0] = l;
            }
            else if ( l < lengths[1] )
            {
                nums[1] = j;
                lengths[1] = l;
            }
        }

        for ( j = 0 ; j < 2 ; j++ )
        {
            v1 = xyz + 4 * edgeVerts[nums[j]][0];
            v2 = xyz + 4 * edgeVerts[nums[j]][1];

            mid[j][0] = 0.5f * (v1[0] + v2[0]);
            mid[j][1] = 0.5f * (v1[1] + v2[1]);
            mid[j][2] = 0.5f * (v1[2] + v2[2]);
        }

        // find the vector of the major axis
        VectorSubtract( mid[1], mid[0], major );

        // cross this with the view direction to get minor axis
        CrossProduct( major, forward, minor );
        VectorNormalize( minor );

        // re-project the points
        for ( j = 0 ; j < 2 ; j++ )
        {
            float	l;

            v1 = xyz + 4 * edgeVerts[nums[j]][0];
            v2 = xyz + 4 * edgeVerts[nums[j]][1];

            l = 0.5 * sqrt( lengths[j] );

            // we need to see which direction this edge
            // is used to determine direction of projection
            for ( k = 0 ; k < 5 ; k++ )
            {
                if(tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] )
                    break;
            }

            if ( k == 5 )
            {
                VectorMA( mid[j], l, minor, v1 );
                VectorMA( mid[j], -l, minor, v2 );
            }
            else
            {
                VectorMA( mid[j], -l, minor, v1 );
                VectorMA( mid[j], l, minor, v2 );
            }
        }
    }
}

/*
=====================
RB_DeformTessGeometry
 
=====================
*/
void RB_DeformTessGeometry( void )
{
    int		i;
    deformStage_t	*ds;

    for ( i = 0 ; i < tess.shader->numDeforms ; i++ )
    {
        ds = &tess.shader->deforms[ i ];

        switch ( ds->deformation )
        {
        case DEFORM_NONE:
            break;
        case DEFORM_NORMALS:
            RB_CalcDeformNormals( ds );
            break;
        case DEFORM_WAVE:
            RB_CalcDeformVertexes( ds );
            break;
        case DEFORM_BULGE:
            RB_CalcBulgeVertexes( ds );
            break;
        case DEFORM_MOVE:
            RB_CalcMoveVertexes( ds );
            break;
        case DEFORM_PROJECTION_SHADOW:
            RB_ProjectionShadowDeform();
            break;
        case DEFORM_AUTOSPRITE:
            AutospriteDeform();
            break;
        case DEFORM_AUTOSPRITE2:
            Autosprite2Deform();
            break;
        case DEFORM_TEXT0:
        case DEFORM_TEXT1:
        case DEFORM_TEXT2:
        case DEFORM_TEXT3:
        case DEFORM_TEXT4:
        case DEFORM_TEXT5:
        case DEFORM_TEXT6:
        case DEFORM_TEXT7:
            DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] );
            break;
        }
    }
}

/*
====================================================================
 
COLORS
 
====================================================================
*/

#define copyToColors(from, to)					\
do{												\
	int i;										\
	for(i=0; i<tess.numVertexes; i++, to+=4)	\
	{											\
		to[0] = from[0];						\
		to[1] = from[1];						\
		to[2] = from[2];						\
		to[3] = from[3];						\
	}											\
} while(0)

/*
** RB_CalcColorFromEntity
*/
void RB_CalcColorFromEntity( float *dstColors )
{
    vec4_t c;

    if ( !backEnd.currentEntity )
        return;

    c[0] = backEnd.currentEntity->e.shaderRGBA[0] * (1.0/256.0);
    c[1] = backEnd.currentEntity->e.shaderRGBA[1] * (1.0/256.0);
    c[2] = backEnd.currentEntity->e.shaderRGBA[2] * (1.0/256.0);
    c[3] = backEnd.currentEntity->e.shaderRGBA[3] * (1.0/256.0);

    copyToColors(c, dstColors);
}

/*
** RB_CalcColorFromOneMinusEntity
*/
void RB_CalcColorFromOneMinusEntity( float *dstColors )
{
    vec4_t c;

    if ( !backEnd.currentEntity )
        return;

    c[0] = 1.0 - (backEnd.currentEntity->e.shaderRGBA[0] * (1.0/256.0));
    c[1] = 1.0 - (backEnd.currentEntity->e.shaderRGBA[1] * (1.0/256.0));
    c[2] = 1.0 - (backEnd.currentEntity->e.shaderRGBA[2] * (1.0/256.0));
    c[3] = 1.0 - (backEnd.currentEntity->e.shaderRGBA[3] * (1.0/256.0));	// this trashes alpha, but the AGEN block fixes it

    copyToColors(c, dstColors);
}

/*
** RB_CalcAlphaFromEntity
*/
void RB_CalcAlphaFromEntity( float *dstColors )
{
    int	i;

    if ( !backEnd.currentEntity )
        return;

    for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
    {
        dstColors[3] = backEnd.currentEntity->e.shaderRGBA[3] * (1.0/256.0);
    }
}

/*
** RB_CalcAlphaFromOneMinusEntity
*/
void RB_CalcAlphaFromOneMinusEntity( float *dstColors )
{
    int	i;

    if ( !backEnd.currentEntity )
        return;

    for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
    {
        dstColors[3] = 1.0 - (backEnd.currentEntity->e.shaderRGBA[3]  * (1.0/256.0));
    }
}

/*
** RB_CalcWaveColor
*/
void RB_CalcWaveColor( const waveForm_t *wf, float *dstColors )
{
    float glow;
    vec4_t	c;

    if ( wf->func == GF_NOISE )
        glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude;
    else
        glow = EvalWaveForm( wf ) * tr.identityLight;

    if ( glow < 0 )
        glow = 0;
    else if ( glow > 1 )
        glow = 1;

    c[0] = c[1] = c[2] = glow;
    c[3] = 1.0;

    copyToColors(c, dstColors);
}

/*
** RB_CalcWaveAlpha
*/
void RB_CalcWaveAlpha( const waveForm_t *wf, float *dstColors )
{
    int i;
    int v;
    float glow;

    glow = EvalWaveFormClamped( wf );

    v = glow;

    for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
    {
        dstColors[3] = v;
    }
}

/*
** RB_CalcModulateColorsByFog
*/
void RB_CalcModulateColorsByFog( float *colors )
{
    int		i;
    float	texCoords[SHADER_MAX_VERTEXES][2];

    // calculate texcoords so we can derive density
    // this is not wasted, because it would only have
    // been previously called if the surface was opaque
    RB_CalcFogTexCoords( texCoords[0], tr.world->fogs + tess.fogNum );

    for ( i = 0; i < tess.numVertexes; i++, colors += 4 )
    {
        float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
        colors[0] *= f;
        colors[1] *= f;
        colors[2] *= f;
    }
}

/*
** RB_CalcModulateAlphasByFog
*/
void RB_CalcModulateAlphasByFog( float *colors )
{
    int		i;
    float	texCoords[SHADER_MAX_VERTEXES][2];

    // calculate texcoords so we can derive density
    // this is not wasted, because it would only have
    // been previously called if the surface was opaque
    RB_CalcFogTexCoords( texCoords[0], tr.world->fogs + tess.fogNum );

    for ( i = 0; i < tess.numVertexes; i++, colors += 4 )
    {
        float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
        colors[3] *= f;
    }
}

/*
** RB_CalcModulateRGBAsByFog
*/
void RB_CalcModulateRGBAsByFog( float *colors )
{
    int		i;
    float	texCoords[SHADER_MAX_VERTEXES][2];

    // calculate texcoords so we can derive density
    // this is not wasted, because it would only have
    // been previously called if the surface was opaque
    RB_CalcFogTexCoords( texCoords[0], tr.world->fogs + tess.fogNum );

    for ( i = 0; i < tess.numVertexes; i++, colors += 4 )
    {
        float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
        colors[0] *= f;
        colors[1] *= f;
        colors[2] *= f;
        colors[3] *= f;
    }
}


/*
====================================================================
 
TEX COORDS
 
====================================================================
*/

/*
========================
RB_CalcFogTexCoords
 
To do the clipped fog plane really correctly, we should use
projected textures, but I don't trust the drivers and it
doesn't fit our shader data.
========================
*/
void RB_CalcFogTexCoords( float *st, fog_t *fog )
{
    int			i;
    float		*v;
    float		s, t;
    float		eyeT;
    qboolean	eyeOutside;
    vec3_t		local;
    vec4_t		fogDistanceVector, fogDepthVector;

    fogDepthVector[0] = 0;
    fogDepthVector[1] = 0;
    fogDepthVector[2] = 0;
    fogDepthVector[3] = 0;

    // all fogging distance is based on world Z units
    VectorSubtract( backEnd.ori.origin, backEnd.viewParms.ori.origin, local );
    fogDistanceVector[0] = -backEnd.ori.modelMatrix[2];
    fogDistanceVector[1] = -backEnd.ori.modelMatrix[6];
    fogDistanceVector[2] = -backEnd.ori.modelMatrix[10];
    fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.ori.axis[0] );

    // scale the fog vectors based on the fog's thickness
    fogDistanceVector[0] *= fog->tcScale;
    fogDistanceVector[1] *= fog->tcScale;
    fogDistanceVector[2] *= fog->tcScale;
    fogDistanceVector[3] *= fog->tcScale;

    // rotate the gradient vector for this orientation
    if ( fog->hasSurface )
    {
        fogDepthVector[0] = fog->surface[0] * backEnd.ori.axis[0][0] +
                            fog->surface[1] * backEnd.ori.axis[0][1] + fog->surface[2] * backEnd.ori.axis[0][2];
        fogDepthVector[1] = fog->surface[0] * backEnd.ori.axis[1][0] +
                            fog->surface[1] * backEnd.ori.axis[1][1] + fog->surface[2] * backEnd.ori.axis[1][2];
        fogDepthVector[2] = fog->surface[0] * backEnd.ori.axis[2][0] +
                            fog->surface[1] * backEnd.ori.axis[2][1] + fog->surface[2] * backEnd.ori.axis[2][2];
        fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.ori.origin, fog->surface );

        eyeT = DotProduct( backEnd.ori.viewOrigin, fogDepthVector ) + fogDepthVector[3];
    }
    else
    {
        eyeT = 1;	// non-surface fog always has eye inside
    }

    // see if the viewpoint is outside
    // this is needed for clipping distance even for constant fog

    if ( eyeT < 0 )
    {
        eyeOutside = qtrue;
    }
    else
    {
        eyeOutside = qfalse;
    }

    fogDistanceVector[3] += 1.0/512;

    // calculate density for each point
    for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4)
    {
        // calculate the length in fog
        s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3];
        t = DotProduct( v, fogDepthVector ) + fogDepthVector[3];

        // partially clipped fogs use the T axis
        if ( eyeOutside )
        {
            if ( t < 1.0 )
            {
                t = 1.0/32;	// point is outside, so no fogging
            }
            else
            {
                t = 1.0/32 + 30.0/32 * t / ( t - eyeT );	// cut the distance at the fog plane
            }
        }
        else
        {
            if ( t < 0 )
            {
                t = 1.0/32;	// point is outside, so no fogging
            }
            else
            {
                t = 31.0/32;
            }
        }

        st[0] = s;
        st[1] = t;
        st += 2;
    }
}

/*
** RB_CalcEnvironmentTexCoords
*/
void RB_CalcEnvironmentTexCoords( float *st )
{
    int			i;
    float		*v, *normal;
    vec3_t		viewer, reflected;
    float		d;

    v = tess.xyz[0];
    normal = tess.normal[0];

    for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 )
    {
        VectorSubtract (backEnd.ori.viewOrigin, v, viewer);
        VectorNormalizeFast (viewer);

        d = DotProduct (normal, viewer);

        reflected[0] = normal[0]*2*d - viewer[0];
        reflected[1] = normal[1]*2*d - viewer[1];
        reflected[2] = normal[2]*2*d - viewer[2];

        st[0] = 0.5 + reflected[1] * 0.5;
        st[1] = 0.5 - reflected[2] * 0.5;
    }
}

/*
** RB_CalcTurbulentTexCoords
*/
void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st )
{
    int i;
    float now;

    now = ( wf->phase + tess.shaderTime * wf->frequency );

    for ( i = 0; i < tess.numVertexes; i++, st += 2 )
    {
        float s = st[0];
        float t = st[1];

        st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude;
        st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude;
    }
}

/*
** RB_CalcScaleTexCoords
*/
void RB_CalcScaleTexCoords( const float scale[2], float *st )
{
    int i;

    for ( i = 0; i < tess.numVertexes; i++, st += 2 )
    {
        st[0] *= scale[0];
        st[1] *= scale[1];
    }
}

/*
** RB_CalcScrollTexCoords
*/
void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st )
{
    int i;
    float timeScale = tess.shaderTime;
    float adjustedScrollS, adjustedScrollT;

    adjustedScrollS = scrollSpeed[0] * timeScale;
    adjustedScrollT = scrollSpeed[1] * timeScale;

    // clamp so coordinates don't continuously get larger, causing problems
    // with hardware limits
    adjustedScrollS = adjustedScrollS - floor( adjustedScrollS );
    adjustedScrollT = adjustedScrollT - floor( adjustedScrollT );

    for ( i = 0; i < tess.numVertexes; i++, st += 2 )
    {
        st[0] += adjustedScrollS;
        st[1] += adjustedScrollT;
    }
}

/*
** RB_CalcTransformTexCoords
*/
void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st  )
{
    int i;

    for ( i = 0; i < tess.numVertexes; i++, st += 2 )
    {
        float s = st[0];
        float t = st[1];

        st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0];
        st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1];
    }
}

/*
** RB_CalcRotateTexCoords
*/
void RB_CalcRotateTexCoords( float degsPerSecond, float *st )
{
    float timeScale = tess.shaderTime;
    float degs;
    int index;
    float sinValue, cosValue;
    texModInfo_t tmi;

    degs = -degsPerSecond * timeScale;
    index = degs * ( FUNCTABLE_SIZE / 360.0f );

    sinValue = tr.sinTable[ index & FUNCTABLE_MASK ];
    cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ];

    tmi.matrix[0][0] = cosValue;
    tmi.matrix[1][0] = -sinValue;
    tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue;

    tmi.matrix[0][1] = sinValue;
    tmi.matrix[1][1] = cosValue;
    tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue;

    RB_CalcTransformTexCoords( &tmi, st );
}

/*
** RB_CalcSpecularAlpha
**
** Calculates specular coefficient and places it in the alpha channel
*/
vec3_t lightOrigin = { -960, 1980, 96 };		// FIXME: track dynamically

void RB_CalcSpecularAlpha( float *alphas )
{
    int			i;
    float		*v, *normal;
    vec3_t		viewer,  reflected;
    float		l, d;
    float		b;
    vec3_t		lightDir;
    int			numVertexes;

    v = tess.xyz[0];
    normal = tess.normal[0];

    numVertexes = tess.numVertexes;
    for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4)
    {
        float ilength;

        VectorSubtract( lightOrigin, v, lightDir );
//		ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) );
        VectorNormalizeFast( lightDir );

        // calculate the specular color
        d = DotProduct (normal, lightDir);
//		d *= ilength;

        // we don't optimize for the d < 0 case since this tends to
        // cause visual artifacts such as faceted "snapping"
        reflected[0] = normal[0]*2*d - lightDir[0];
        reflected[1] = normal[1]*2*d - lightDir[1];
        reflected[2] = normal[2]*2*d - lightDir[2];

        VectorSubtract (backEnd.ori.viewOrigin, v, viewer);
        ilength = Q_rsqrt( DotProduct( viewer, viewer ) );
        l = DotProduct (reflected, viewer);
        l *= ilength;

        if (l < 0)
        {
            b = 0;
        }
        else
        {
            l = l*l;
            l = l*l;
            b = l * 1.0;
            if (b > 5.0)
            {
                b = 5.0;
            }
        }

        alphas[3] = b;
    }
}

/*
** RB_CalcDiffuseColor
**
** The basic vertex lighting calc
*/
void RB_CalcDiffuseColor( float *colors )
{
#if idppc_altivec
    int				i, j;
    float			*v, *normal;
    float			incoming;
    trRefEntity_t	*ent;
    vec3_t			ambientLight;
    vec3_t			lightDir;
    vec3_t			directedLight;
    int				numVertexes;

    vector unsigned char vSel = (vector unsigned char)(0x00, 0x00, 0x00, 0xff,
                                0x00, 0x00, 0x00, 0xff,
                                0x00, 0x00, 0x00, 0xff,
                                0x00, 0x00, 0x00, 0xff);
    vector float ambientLightVec;
    vector float directedLightVec;
    vector float lightDirVec;
    vector float normalVec0, normalVec1;
    vector float incomingVec0, incomingVec1, incomingVec2;
    vector float zero, jVec;
    vector signed int jVecInt;
    vector signed short jVecShort;
    vector unsigned char jVecChar, normalPerm;

    ent = backEnd.currentEntity;

    // A lot of this could be simplified if we made sure
    // entities light info was 16-byte aligned.
    jVecChar = vec_lvsl(0, ent->ambientLight);
    ambientLightVec = vec_ld(0, (vector float *)ent->ambientLight);
    jVec = vec_ld(11, (vector float *)ent->ambientLight);
    ambientLightVec = vec_perm(ambientLightVec,jVec,jVecChar);

    jVecChar = vec_lvsl(0, ent->directedLight);
    directedLightVec = vec_ld(0,(vector float *)ent->directedLight);
    jVec = vec_ld(11,(vector float *)ent->directedLight);
    directedLightVec = vec_perm(directedLightVec,jVec,jVecChar);

    jVecChar = vec_lvsl(0, ent->lightDir);
    lightDirVec = vec_ld(0,(vector float *)ent->lightDir);
    jVec = vec_ld(11,(vector float *)ent->lightDir);
    lightDirVec = vec_perm(lightDirVec,jVec,jVecChar);

    zero = (vector float)vec_splat_s8(0);
    VectorCopy( ent->lightDir, lightDir );

    v = tess.xyz[0];
    normal = tess.normal[0];

    normalPerm = vec_lvsl(0,normal);
    numVertexes = tess.numVertexes;
    for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4)
    {
        normalVec0 = vec_ld(0,(vector float *)normal);
        normalVec1 = vec_ld(11,(vector float *)normal);
        normalVec0 = vec_perm(normalVec0,normalVec1,normalPerm);
        incomingVec0 = vec_madd(normalVec0, lightDirVec, zero);
        incomingVec1 = vec_sld(incomingVec0,incomingVec0,4);
        incomingVec2 = vec_add(incomingVec0,incomingVec1);
        incomingVec1 = vec_sld(incomingVec1,incomingVec1,4);
        incomingVec2 = vec_add(incomingVec2,incomingVec1);
        incomingVec0 = vec_splat(incomingVec2,0);
        incomingVec0 = vec_max(incomingVec0,zero);
        normalPerm = vec_lvsl(12,normal);
        jVec = vec_madd(incomingVec0, directedLightVec, ambientLightVec);
        jVecInt = vec_cts(jVec,0);	// RGBx
        jVecShort = vec_pack(jVecInt,jVecInt);		// RGBxRGBx
        jVecChar = vec_packsu(jVecShort,jVecShort);	// RGBxRGBxRGBxRGBx
        jVecChar = vec_sel(jVecChar,vSel,vSel);		// RGBARGBARGBARGBA replace alpha with 255
        vec_ste((vector unsigned int)jVecChar,0,(unsigned int *)&colors[i*4]);	// store color
    }
#else
    int				i;
    float			*v, *normal;
    float			incoming;
    trRefEntity_t	*ent;
    vec3_t			ambientLight;
    vec3_t			lightDir;
    vec3_t			directedLight;
    int				numVertexes;

    ent = backEnd.currentEntity;

    VectorCopy( ent->ambientLight, ambientLight );
    VectorCopy( ent->directedLight, directedLight );
    VectorCopy( ent->lightDir, lightDir );

    v = tess.xyz[0];
    normal = tess.normal[0];

    numVertexes = tess.numVertexes;
    for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4)
    {
        incoming = DotProduct (normal, lightDir);
        if ( incoming <= 0 )
        {
            colors[i*4+0] = ent->ambientLightInt[0];
            colors[i*4+1] = ent->ambientLightInt[1];
            colors[i*4+2] = ent->ambientLightInt[2];
            colors[i*4+3] = ent->ambientLightInt[3];
            continue;
        }

        colors[i*4+0] = min(5.0, ambientLight[0] + (incoming * directedLight[0]));
        colors[i*4+1] = min(5.0, ambientLight[1] + (incoming * directedLight[1]));
        colors[i*4+2] = min(5.0, ambientLight[2] + (incoming * directedLight[2]));
        colors[i*4+3] = 1.0;
    }
#endif
}

